/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/*
 * UltimatumDeterministicStrategy.java
 *
 * Created on 15/Dez/2011, 11:52:28
 */

package intopo.game.impl.ultimatum;

import intopo.evogame.partnerselection.config.AbstractTextConfigFile;
import intopo.evogame.partnerselection.config.ParsingException;
import intopo.util.Util;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Scanner;

/**
 *
 * @author <a href="mailto:mariano.pedro@gmail.com">Pedro Mariano</a>
 *
 * @version 1.0 15/Dez/2011
 */
public class UltimatumDeterministicStrategy
	extends UltimatumStrategy
{
	/**
	 * Constructor tag used in text streams and in the map ADT.
	 */
	static final String CONSTRUCTOR_TAG = "UltimatumDeterministicStrategy";
	/**
	 * Parameters that control the behaviour of all instances of class {@code UltimatumDeterministicStrategy}.
	 */
	static class Parameters
	{
		/**
		 * Standard deviation of the normal distribution used to mutate the division proposal parameter.
		 */
		static float stddevDivisionProposal = 1;
		/**
		 * Probability to flip an accept division parameter.
		 */
		static float flipProbabilityAcceptDivision;
		/**
		 *
		 * @param scanner
		 */
		static void read (Scanner scanner)
			throws ParsingException
		{
			Parameters.stddevDivisionProposal = AbstractTextConfigFile.readFloat (scanner, "standard deviation of the normal distribution used to mutate the division proposal parameter");
			Parameters.flipProbabilityAcceptDivision = AbstractTextConfigFile.readFloat (scanner, "Probability to flip an accept division parameter");
		}
		/**
		 * 
		 * @param ps
		 */
		static void write (PrintStream ps)
		{
			ps.print (Parameters.stddevDivisionProposal);
			ps.println ("\t# standard deviation of the normal distribution used to mutate the division proposal parameter");
			ps.print (Parameters.flipProbabilityAcceptDivision);
			ps.println ("\t# Probability to flip an accept division parameter");
		}
	}
	/**
	 * The division that this strategy proposes.  The value in this field is used in conjunction with field {@code numberDivisions}.
	 */
	final byte divisionProposal;
	/**
	 * A boolean vector that holds the answer to a proposition to divide the cake.
	 */
	final boolean[] acceptsDivision;
	/**
	 *
	 * @param divisionProposal
	 * @param acceptsDivision
	 */
	public UltimatumDeterministicStrategy (byte divisionProposal, boolean[] acceptsDivision)
	{
		assert acceptsDivision.length == UltimatumStrategy.Parameters.cakeSize + 1;
		this.divisionProposal = divisionProposal;
		this.acceptsDivision = acceptsDivision;
//		this.acceptsDivision = new boolean [UltimatumStrategy.Parameters.cakeSize + 1];
//		System.arraycopy(acceptsDivision, 0, this.acceptsDivision, 0, UltimatumStrategy.Parameters.cakeSize + 1);
	}
	/**
	 *
	 * @param scanner
	 */
	public UltimatumDeterministicStrategy (Scanner scanner)
	{
		this.divisionProposal = scanner.nextByte ();
		this.acceptsDivision = new boolean [UltimatumStrategy.Parameters.cakeSize + 1];
		for (int i = 0; i <= UltimatumStrategy.Parameters.cakeSize; i++) {
			this.acceptsDivision [i] = scanner.nextBoolean ();
		}
	}
	/**
	 *
	 * @return
	 */
	@Override
	public int getDivisionProposal ()
	{
		return this.divisionProposal;
	}
	/**
	 *
	 * @param divisionProposal
	 * @return
	 */
	@Override
	public boolean isDivisionAcceptable (int divisionProposal)
	{
		return this.acceptsDivision [divisionProposal];
	}
	/**
	 * This strategy can play both roles of the <i>Ultimatum</i> game.  This method always returns {@code Role.BOTH}.
	 *
	 * @return the value {@code Role.BOTH}.
	 */
	@Override
	public Role getPlayableRoles ()
	{
		return Role.BOTH;
	}
	/**
	 * Get the cake size this strategy is going to use.
	 *
	 * @return
	 */
	@Override
	public int getCakeSize ()
	{
		return this.acceptsDivision.length - 1;
	}
	/**
	 *
	 * @return
	 */
	@Override
	public UltimatumStrategy getMutantAllGenes ()
	{
		byte divisionProposalMut = (byte) Util.perturbNormal (
				this.divisionProposal,
				(double) UltimatumDeterministicStrategy.Parameters.stddevDivisionProposal,
				0,
				this.acceptsDivision.length);
		boolean[] acceptsDivisionMut = new boolean [this.acceptsDivision.length];
		for (int i = this.acceptsDivision.length; i >= 0; i--) {
			if (Util.flipCoin (UltimatumDeterministicStrategy.Parameters.flipProbabilityAcceptDivision)) {
				acceptsDivisionMut [i] = !this.acceptsDivision [i];
			}
			else {
				acceptsDivisionMut [i] = this.acceptsDivision [i];
			}
		}
		return new UltimatumDeterministicStrategy (divisionProposalMut, acceptsDivisionMut);
	}

	@Override
	public UltimatumStrategy getMutantSingleGene ()
	{
		byte divisionProposalMut = this.divisionProposal;
		boolean[] acceptsDivisionMut = java.util.Arrays.copyOf (this.acceptsDivision, this.acceptsDivision.length);
		int gene = Util.nextPositiveInt (2 + UltimatumStrategy.Parameters.cakeSize);
		if (gene == 1 + UltimatumStrategy.Parameters.cakeSize) {
			divisionProposalMut = (byte) Util.perturbNormal (
				this.divisionProposal,
				(double) UltimatumDeterministicStrategy.Parameters.stddevDivisionProposal,
				0,
				this.acceptsDivision.length);
		}
		else {
			if (Util.flipCoin (UltimatumDeterministicStrategy.Parameters.flipProbabilityAcceptDivision)) {
				acceptsDivisionMut [gene] = !this.acceptsDivision [gene];
			}
		}
		return new UltimatumDeterministicStrategy (divisionProposalMut, acceptsDivisionMut);
	}
	/**
	 *
	 * @return
	 */
	@Override
	public int getBin ()
	{
		int bin = 0;
		for (int i = 0; i < this.acceptsDivision.length; i++) {
			if (this.acceptsDivision [i]) {
				bin += 1 << i;
			}
		}
		bin *= this.acceptsDivision.length;
		bin += this.divisionProposal;
		return bin;
	}
	/**
	 * Get a description of the strategy that has the given bin.
	 *
	 * <p> This method relies on parameter {@code UltimatumStrategy.Parameters.cakeSize} to compute the bin.  After transforming the parameter value, it must contain the value {@code 0}.
	 *
	 * @param bin
	 * @return a description of the strategy that has the given bin.
	 */
	static String getBinDescription (int bin)
	{
		final int savedBin = bin;
		StringBuffer result = new StringBuffer (3 + 1 + UltimatumStrategy.Parameters.cakeSize);
		result.append (bin % UltimatumStrategy.Parameters.cakeSize);
		bin /= UltimatumStrategy.Parameters.cakeSize;
		result.append (' ');
		for (int i = 0; i < UltimatumStrategy.Parameters.cakeSize; i++) {
			result.append ((bin & 1) == 1 ? 'X' : '-');
			bin = bin >> 1;
		}
		assert (bin == 0) : "The cake size does not correspond to bin value " + savedBin;
		return result.toString ();
	}
	/**
	 *
	 * @return
	 */
	@Override
	public boolean isCooperator ()
	{
		if (this.divisionProposal >= UltimatumStrategy.Parameters.cakeSize / 2) {
			for (int i = UltimatumStrategy.Parameters.cakeSize / 2; i < this.acceptsDivision.length; i++) {
				if (!this.acceptsDivision [i]) {
					return false;
				}
			}
			return true;
		}
		else {
			return false;
		}
	}
	/**
	 *
	 * @param ps
	 */
	@Override
	public void print (PrintStream ps)
	{
		ps.print (UltimatumDeterministicStrategy.CONSTRUCTOR_TAG);
		ps.print (' ');
		ps.print (this.divisionProposal);
		for (int i = 0; i < this.acceptsDivision.length; i++) {
			ps.print (' ');
			ps.print (this.acceptsDivision [i]);
		}
	}
	/**
	 *
	 * @param dos
	 * @throws java.io.IOException
	 */
	@Override
	public void write (DataOutputStream dos)
		throws IOException
	{
		dos.writeByte (this.divisionProposal);
		for (int i = 0; i < this.acceptsDivision.length; i++) {
			dos.writeBoolean (this.acceptsDivision [i]);
		}
	}
	@Override
	public String toString ()
	{
		StringBuffer result = new StringBuffer (3 + 1 + this.acceptsDivision.length);
		result.append (this.divisionProposal);
		result.append (' ');
		for (int i = 0; i < this.acceptsDivision.length; i++) {
			result.append (this.acceptsDivision [i] ? 'X' : '-');
		}
		return result.toString ();
	}
}

// Local Variables:
// mode: java
// mode: flyspell-prog
// ispell-local-dictionary: "british"
// End:
